/**
 * 
 */
package org.jinion.util;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jinion.exception.BeanMethodInvokeException;
import org.jinion.exception.JinionRuntimeException;

/**
 * This class is used to prepare the data (key, value) to be inserted into database table.
 * This class can use reflection to get the data from the provided bean.
 * NOTE: This class only works with standard java bean!
 * @author zhlmmc
 *
 */
public class BeanMap extends HashMap<Object, Object> {
	private static final Log log = LogFactory.getLog(BeanMap.class);
	
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	
	/**
	 * Extract data from the given bean and save the data (key, value).
	 * @param bean
	 * @throws BeanMethodInvokeException
	 */
	public BeanMap(Object bean) throws BeanMethodInvokeException{
		log.debug("Creating DataMap for bean " + bean.getClass().getSimpleName());
		this.extractData(bean);
	}
	
	/**
	 * Put the given data into the map.
	 * @param data
	 */
	public BeanMap(Map<String, Object> data){
		this.putAll(data);
	}

	/**
	 * If the bean property name and database table column name doesn't match.
	 * Use this method to fix it.
	 * @param oldKey
	 * @param newKey
	 */
	public void updateKeyName(Object oldKey, Object newKey){
		if (containsKey(oldKey)) {
			Object value = get(oldKey);
			remove(oldKey);
			put(newKey, value);
		}
		else{
			throw new JinionRuntimeException("OldKey doesn't exist!");
		}
	}
	
	/**
	 * Fill the given bean's properties with the data of this map. <br>
	 * The bean's property will be set if the property satisfies: <br>
	 * 1. The property has setter. <br>
	 * 2. This map has a key which is the same as the property name. <br>
	 * 3. The type of the value in the map can be converted to the type of the parameter of the setter.
	 * @param bean
	 */
	@SuppressWarnings("unchecked")
	public void fillBean(Object bean){
		Method[] methods = bean.getClass().getMethods();
		for (Method method : methods) {
			if (!method.getName().startsWith("set")) {
				continue;
			}
			String name = method.getName();
			String propertyName = name.substring(3);
			String temp = propertyName.substring(0, 1);
			propertyName = propertyName.replaceFirst(temp, temp.toLowerCase());
			Object value = this.get(propertyName);
			if (value == null) {
				continue;
			}
			
			Class[] ptypes = method.getParameterTypes();
			if (ptypes.length < 1) {
				continue;
			}
			Class type = ptypes[0];
			try {
				if (type == Boolean.TYPE) {
					if (((Integer)value).equals(0)) {
						value = Boolean.FALSE;
					} else if(((Integer)value).equals(1)){
						value = Boolean.TRUE;
					} else{
						continue;
					}
				} else if(type == Integer.TYPE){
					if (value instanceof String) {
						value = Integer.valueOf((String)value);
					} else if(value instanceof Integer){
						value = (Integer)value;
					} else if(value instanceof Long){
						value = ((Long)value).intValue();
					} else{
						continue;
					}
				} else if(type == Long.TYPE){
					if (value instanceof String) {
						value = Long.valueOf((String)value);
					} else if(value instanceof Integer){
						value = (Long)value;
					} else if(value instanceof Long){
						value = (Long)value;
					} else{
						continue;
					}
				}
			} catch (ClassCastException e) {
				// Ignore
			} catch (Exception e){
				continue;
			}
			
			try {
				method.invoke(bean, value);
			} catch (IllegalArgumentException e) {
				// Ignore
			} catch (Exception e) {
				throw new BeanMethodInvokeException(e);
			} 
		}
	}
	
	private void extractData(Object bean) throws BeanMethodInvokeException{
		Method[] methods = bean.getClass().getMethods();
		for (int i = 0; i < methods.length; i++) {
			if (methods[i].getName().startsWith("get")) {
				String name = methods[i].getName();
				String propertyName = name.substring(3);
				String temp = propertyName.substring(0, 1);
				propertyName = propertyName.replaceFirst(temp, temp.toLowerCase());
				try {
					put(propertyName, methods[i].invoke(bean));
				} catch (Exception e) {
					throw new BeanMethodInvokeException(e);
				} 
			}
			else if(methods[i].getName().startsWith("is")){
				String name = methods[i].getName();
				String propertyName = name.substring(2);
				String temp = propertyName.substring(0, 1);
				propertyName = propertyName.replaceFirst(temp, temp.toLowerCase());
				try {
					if (methods[i].invoke(bean).equals(Boolean.TRUE)) {
						super.put(propertyName, 1);
					}
					else{
						super.put(propertyName, 0);
					}
				} catch (Exception e) {
					throw new BeanMethodInvokeException(e);
				} 
			}
		}
	}
}
